package com.shm.leetcode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * 1631. 最小体力消耗路径
 * 你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ，其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ，且你希望去最右下角的格子 (rows-1, columns-1) （注意下标从 0 开始编号）。你每次可以往 上，下，左，右 四个方向之一移动，你想要找到耗费 体力 最小的一条路径。
 *
 * 一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。
 *
 * 请你返回从左上角走到右下角的最小 体力消耗值 。
 *
 *
 *
 * 示例 1：
 *
 *
 *
 * 输入：heights = [[1,2,2],[3,8,2],[5,3,5]]
 * 输出：2
 * 解释：路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。
 * 这条路径比路径 [1,2,2,2,5] 更优，因为另一条路径差值最大值为 3 。
 * 示例 2：
 *
 *
 *
 * 输入：heights = [[1,2,3],[3,8,4],[5,3,5]]
 * 输出：1
 * 解释：路径 [1,2,3,4,5] 的相邻格子差值绝对值最大为 1 ，比路径 [1,3,5,3,5] 更优。
 * 示例 3：
 *
 *
 * 输入：heights = [[1,2,1,1,1],[1,2,1,2,1],[1,2,1,2,1],[1,2,1,2,1],[1,1,1,2,1]]
 * 输出：0
 * 解释：上图所示路径不需要消耗任何体力。
 *
 *
 * 提示：
 *
 * rows == heights.length
 * columns == heights[i].length
 * 1 <= rows, columns <= 100
 * 1 <= heights[i][j] <= 106
 * @author SHM
 */
public class MinimumEffortPath {
    /**
     * 方法二：并查集
     * 思路与算法
     *
     * 我们将这 mnmn 个节点放入并查集中，实时维护它们的连通性。
     *
     * 由于我们需要找到从左上角到右下角的最短路径，因此我们可以将图中的所有边按照权值从小到大进行排序，并依次加入并查集中。当我们加入一条权值为 xx 的边之后，如果左上角和右下角从非连通状态变为连通状态，那么 xx 即为答案。
     *
     * 复杂度分析
     *
     * 时间复杂度：O(mn \log(mn))O(mnlog(mn))，其中 mm 和 nn 分别是地图的行数和列数。图中的边数为 O(mn)O(mn)，因此排序的时间复杂度为 O(mn \log (mn))O(mnlog(mn))。并查集的时间复杂度为 O(mn \cdot \alpha(mn))O(mn⋅α(mn))，其中 \alphaα 为阿克曼函数的反函数。由于后者在渐进意义下小于前者，因此总时间复杂度为 O(mn \log(mn))O(mnlog(mn))。
     *
     * 空间复杂度：O(mn)O(mn)，即为存储所有边以及并查集需要的空间。
     *
     * 作者：LeetCode-Solution
     * 链接：https://leetcode-cn.com/problems/path-with-minimum-effort/solution/zui-xiao-ti-li-xiao-hao-lu-jing-by-leetc-3q2j/
     * @param heights
     * @return
     */
    public int minimumEffortPath(int[][] heights) {
        int n = heights.length;
        int m = heights[0].length;
        List<int[]> edges = new ArrayList<>();

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                int index = i*m+j;
                if(i>0){
                    edges.add(new int[]{index-m,index,Math.abs(heights[i][j]-heights[i-1][j])});
                }
                if(j>0){
                    edges.add(new int[]{index-1,index,Math.abs(heights[i][j]-heights[i][j-1])});
                }
            }
        }

        Collections.sort(edges, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[2]-o2[2];
            }
        });

        UnionFind uf = new UnionFind(n*m);
        for (int[] edge : edges) {
            int x = edge[0],y = edge[1],v=edge[2];
            uf.union(x,y);
            if (uf.find(0)==uf.find(n*m-1)){
                return v;
            }
        }

        return 0;
    }
    class UnionFind{
        int[] parent;
        int count;
        UnionFind(int n){
            parent = new int[n];
            count = n;
            for (int i = 0; i < n; i++) {
                parent[i] = i;
            }
        }

        int find(int x){
            if(parent[x]!=x){
                parent[x] = find(parent[x]);
            }
            return parent[x];
        }

        void union(int x, int y){
            int newX = find(x);
            int newY = find(y);
            if(newX==newY){
                return;
            }
            parent[newY] = newX;
            count--;
        }

        int getCount(){
            return count;
        }
    }
}
